開始之前,如果有對 MVC 不熟的大大可以參考文章。在 MVC 當中,其實只有定義了 model、view 和 controller 而已,意思就是說不需要接下來要說的 repository 與 service 依然可以執行各種商業邏輯。
但我們可以想像,把所有商業邏輯都寫在同一個區塊是不是有點過於臃腫,且各項商業邏輯重複的部分也不少,未來光是維護的困難性就頗高,更不用說測試的部分。那我們把大部分邏輯都寫到 model 中吧(嗎)?! 然而 model 的定義是資料表以及資料紀錄 (table record) 對應的類別,所以也不適合寫在這裡面。因此為了將 controller 瘦身,又保持 model 的乾淨,許多人會在 model 與 controller 中間又增加了 repository 和 service 兩層。
其中 repository 主要負責商業邏輯中各種資料庫 CRUD 的部分,而 service 則是統合各項資源與應用 (service 部分明天再談),以下我們會用 Post 作為範例解釋 repository 並進行重構。
namespace App\Repositories;
use App\Models\Post;
class PostRepository
{
public function create($data)
{
$newPost = new Post();
$newPost->user_id = $data['user_id'];
$newPost->title = $data['title'];
$newPost->content = $data['content'];
$newPost->published_at = $data['published_at'];
$saveSuccess = $newPost->save();
if ($saveSuccess) {
return $newPost;
}
throw new \Exception("create failed");
}
public function update($id, $data)
{
$post = Post::find($id);
$post->title = $data['title'];
$updateSuccess = $post->save();
if ($updateSuccess) {
return $post;
}
throw new \Exception("update failed");
}
public function delete($id)
{
return Post::destroy($id);
}
public function readById($id, $collumns = ['*'])
{
return Post::find($id, $collumns);
}
// ...
}
class UserRepository
{
public function create($data)
{
$newUser = new User();
$newUser->name = $data['name'];
$newUser->email = $data['email'];
$newUser->password = $data['password'];
$saveSuccess = $newUser->save();
if ($saveSuccess) {
return $newUser;
}
throw new \Exception("create failed");
}
// ...
namespace App\Repositories;
interface IRepository
{
public function create(array $data);
public function update($id, array $data);
public function delete($id);
public function readById($id);
}
model()
這個方法到個別 repository 再實作。namespace App\Repositories;
use Illuminate\Container\Container as App;
abstract class BaseRepository implements IRepository
{
private $app;
private $modelClass;
public function __construct(App $app)
{
$this->app = $app;
$this->modelClass = $this->model();
}
// 回傳各別 repository 要用的 model
protected abstract function model();
public function create(array $data)
{
$newModelInstance = $this->app->make($this->$modelClass);
return $this->setModelInstance($newModelInstance, $data);
}
public function update($id, array $data)
{
$modelInstance = $this->$modelClass::find($id);
return $this->setModelInstance($modelInstance, $data);
}
protected function setModelInstance($instance, array $data = [])
{
if (isset($instance)) {
foreach($data as $property => $value) {
if (isset($value)) {
$instance[$property] = $value;
}
}
$saveSuccess = $instance->save();
if ($saveSuccess) {
return $instance;
}
}
throw new \Exception("create/update failed");
}
public function delete($id)
{
$this->modelClass::destroy($id);
}
public function readById($id, $collumns = ['*'])
{
return $this->modelClass::find($id, $collumns);
}
// ...
}
class PostRepository extends BaseRepository
{
protected function model() {
return Post::class;
}
// ...
}
Repository 主要的重點在於為 controller 瘦身,同時主要關注在資料表的存取。因此假若未來更換資料庫,例如從 MySql 改為 MongoDB 我們只要更新各個 repositories 即可,不影響到原本的商業邏輯!另外,透過實作 interface 與繼承 abstract class 可以讓各個 repositories 更簡潔並專注在自己特例的部分。
明天我們將繼續介紹 service,把商業邏輯補齊並讓 controller 減重成功! 順帶一提,範例中各種 Eloquent Model 的功能可以參考官方 API。